package com.uex.dev.manage.admin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Preconditions;
import com.google.common.cache.LoadingCache;
import com.uex.dev.manage.admin.config.AdminConfig;
import com.uex.dev.manage.admin.config.WxConfig;
import com.uex.dev.manage.admin.dao.UserDao;
import com.uex.dev.manage.admin.intercepter.OpenidContext;
import com.uex.dev.manage.admin.po.ServiceCodePO;
import com.uex.dev.manage.admin.po.UserPO;
import com.uex.dev.manage.admin.service.IServiceCodeService;
import com.uex.dev.manage.admin.service.IUserService;
import com.uex.dev.manage.admin.vo.user.UserPageReq;
import com.uex.dev.manage.admin.vo.user.UserPageResp;
import com.uex.dev.manage.admin.vo.wx.BindResp;
import com.uex.dev.manage.admin.vo.wx.OpenidInfo;
import com.uex.dev.manage.common.base.PageResult;
import com.uex.dev.manage.common.constant.CommonConstant;
import com.uex.dev.manage.common.utils.GsonUtil;
import com.uex.dev.manage.common.utils.Md5Utils;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author wangchao
 * @since 2024-02-22
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserDao, UserPO> implements IUserService {

    @Resource
    private IServiceCodeService serviceCodeService;

    @Resource(name = "tokenCache")
    private LoadingCache<String, UserPO> cache;

    @Resource(name = "openIdCache")
    private LoadingCache<String, OpenidInfo> openIdCache;

    @Resource
    private AdminConfig adminConfig;

    @Resource
    private WxConfig wxConfig;

    @Override
    public Boolean add(UserPO req) {
        LambdaQueryWrapper<UserPO> wrapper = Wrappers.<UserPO>lambdaQuery()
                .eq(UserPO::getUserAccount, req.getUserAccount());
        UserPO user = new UserPO();
        Preconditions.checkArgument(Objects.isNull(this.getOne(wrapper)), "账号已存在");
        BeanUtils.copyProperties(req, user);
        user.setUserPasswd(Md5Utils.MD5EncodeWithSalt(req.getUserPasswd(), adminConfig.getPasswdSalt()));
        return this.save(user);
    }

    @Override
    public String login(UserPO req) {
        LambdaQueryWrapper<UserPO> wrapper = Wrappers.<UserPO>lambdaQuery()
                .eq(UserPO::getUserAccount, req.getUserAccount())
                .eq(UserPO::getUserPasswd, Md5Utils.MD5EncodeWithSalt(req.getUserPasswd(), adminConfig.getPasswdSalt()));
        UserPO user = this.getOne(wrapper);
        Preconditions.checkArgument(Objects.nonNull(user), "账号或密码错误");
        user.setUserPasswd(null);
        // 保存在缓存中
        String token = UUID.randomUUID().toString();
        cache.put(token, user);
        return token;
    }

    @Override
    public Optional<UserPO> getLoginUser(String token) {
        if (StringUtils.isEmpty(token)) {
            return Optional.empty();
        }
        UserPO user = cache.getIfPresent(token);
        if (user == null) {
            return Optional.empty();
        }
        cache.put(token, user);
        return Optional.of(user);
    }

    @Override
    public PageResult<UserPageResp> selectList(UserPageReq req) {
        Page<UserPO> page = new Page<>(req.getPage(), req.getRecord());
        Page<UserPO> result = this.page(page, new LambdaQueryWrapper<UserPO>()
                .eq(ObjectUtils.isNotNull(req.getUserType()), UserPO::getUserType, req.getUserType())
                .like(StringUtils.isNotBlank(req.getUserAccount()), UserPO::getUserAccount, req.getUserAccount())
                .like(StringUtils.isNotBlank(req.getUserName()), UserPO::getUserName, req.getUserName()));
        return PageResult.build(result).convert(UserPageResp::fromPO);
    }

    @Override
    public BindResp userBind(UserPO userBindReq) {
        String userAccount = userBindReq.getUserAccount();
        String userPasswd = userBindReq.getUserPasswd();
        LambdaQueryWrapper<UserPO> wrapper = Wrappers.<UserPO>lambdaQuery()
                .eq(UserPO::getUserAccount, userAccount)
                .eq(UserPO::getUserPasswd, Md5Utils.MD5EncodeWithSalt(userPasswd, adminConfig.getPasswdSalt()));
        UserPO user = this.getOne(wrapper);
        Preconditions.checkArgument(Objects.nonNull(user), "用户不存在");
        Preconditions.checkArgument(StringUtils.isEmpty(user.getUserWxid()), "当前用户已被绑定");
        OpenidInfo openidInfo = OpenidContext.getOpenidLocal();
        UserPO bindUser = this.getOne(Wrappers.<UserPO>lambdaQuery().eq(UserPO::getUserWxid, openidInfo.getOpenid()));
        Preconditions.checkArgument(Objects.isNull(bindUser), "当前微信用户已绑定");
        user.setUserWxid(openidInfo.getOpenid());
        user.setUserBindTime(LocalDateTime.now());
        openidInfo.setUserId(user.getId());
        openidInfo.setUserAccount(userAccount);
        BindResp bindResp = BindResp.fromPO(user);
        openIdCache.put(openidInfo.getOpenid(), openidInfo);
        this.updateById(user);
        return bindResp;
    }

    @Override
    public BindResp getBind(HttpServletRequest request) {
        String code = request.getHeader(CommonConstant.HEADER_WXID);
        // TODO 预留方便调试
        // 获取openId
        OpenidInfo openidInfo = getOpenId(code);
        String openId = openidInfo.getOpenid();
        // 设置cookie
        request.getSession().setAttribute(CommonConstant.SESSION_KEY, openId);

        LambdaQueryWrapper<UserPO> wrapper = Wrappers.<UserPO>lambdaQuery()
                .eq(UserPO::getUserWxid, openId);
        UserPO user = this.getOne(wrapper);
        // 是否为绑定用户
        BindResp bindResp = new BindResp();
        if (ObjectUtils.isNotNull(user)) {
            bindResp = BindResp.fromPO(user);
            openidInfo.setUserId(user.getId());
            openidInfo.setUserAccount(user.getUserAccount());
        } else {
            // 不是内部用户，则判断是否绑定服务码
            ServiceCodePO serviceCode = serviceCodeService.getOne(new LambdaQueryWrapper<ServiceCodePO>()
                    .eq(ServiceCodePO::getGuestWxid, openId));
            if (ObjectUtils.isNotNull(serviceCode)) {
                bindResp.setGuestPrjid(serviceCode.getGuestPrjid());
            } else {
                bindResp.setGuestPrjid(0);
            }
        }
        openIdCache.put(openId, openidInfo);
        return bindResp;
    }

    // 获取用户openid
    private OpenidInfo getOpenId(String code) {
        StringBuilder result = new StringBuilder()
                .append(wxConfig.getUrl())
                .append("?appid=").append(wxConfig.getAppid())
                .append("&secret=").append(wxConfig.getSecret())
                .append("&js_code=").append(code)
                .append("&grant_type=authorization_code");
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(result.toString())
                .get()
                .build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            String data = response.body().string();
            log.info("wx req: {}", data);
            Preconditions.checkArgument(!data.contains("errcode"), "用户code错误");
            return GsonUtil.fromJson(data, OpenidInfo.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
